%{
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/str_stream.h>
#include <sobjc-commons/str_list.h>
#include <sobjc-commons/builtin_sels.h>
#include <sobjc-commons/utils.h>
#include "context.h"
#include "command.h"
#include "scope.h"
#include "name.h"
#include "patch.h"
#include "salloc.h"
#include "type.h"

#define STORAGE_CLS_SPCR_TYPEDEF    1
#define STORAGE_CLS_SPCR_EXTERN     (1<<1)
#define STORAGE_CLS_SPCR_STATIC     (1<<2)
#define STORAGE_CLS_SPCR_AUTO       (1<<3)
#define STORAGE_CLS_SPCR_REGISTER   (1<<4)

#define TYPE_SPCR_CHAR          1
#define TYPE_SPCR_SHORT         (1<<1)
#define TYPE_SPCR_INT           (1<<2)
#define TYPE_SPCR_LONG          (1<<3)
#define TYPE_SPCR_LONG_LONG     (1<<4)
#define TYPE_SPCR_FLOAT         (1<<5)
#define TYPE_SPCR_DOUBLE        (1<<6)
#define TYPE_SPCR_SIGNED        (1<<7)
#define TYPE_SPCR_UNSIGNED      (1<<8)
#define TYPE_SPCR_VOID          (1<<9)

#define NULL_S          "(void *)0"
#define TEMP_CLASS      "cls"
#define TEMP_IVAR       "ivar"
#define TEMP_INST_METH  "inst_meth"
#define TEMP_CLASS_METH "class_meth"
#define SEL_TABLE_VAR   "__objc_sel_table"
#define IC_TABLE_VAR    "__objc_ic_table"
#define RECV_VAR        "__objc_recv"
#define ATTR_UNUSED     "__attribute__((unused))"

#define GENERATE    1 
#define GENERATED   2

typedef enum {
  BOTH_NOT_OBJECT,
  CLASS_OBJECT,
  ROOT_CLASS,
  SAME_CLASS,
  COMPATIBLE_CLASS,
  INCOMPATIBLE_TYPE,
  INCOMPATIBLE_CLASS
} CLASS_VALIDATION_RESULT;

struct class_validation_result {
  int type;
  objc_class_t cls;
};

int yylex();
static int yyparse();
void yyerror(const char *);
static void parse_error(const char *, ...);
static void parse_warn(const char *, ...);
static void parse_logv(const char *, const char *, va_list);
static void begin_scope();
static void end_scope();
static void set_func(objc_node_t, objc_node_t, objc_node_t);
static void validate_storage_cls_spcr(int);
static objc_node_t validate_type_spcr_and_make_node(int);
static int type_spcr_tag_to_flag(int);
static char *generate_kw_sel(objc_node_t);
static void validate_protocol(char *);
static char *get_all_ivar_defs(objc_class_t, BOOL);
static void add_superclass_ivar_patch(size_t, objc_class_t);
static void add_class_struct_patch(size_t);
static void add_ivar_ref_patch(size_t, const char *);
static void validate_all_meth_defs();
static void validate_meth_defs(objc_class_t, BOOL);
static void set_func_args(objc_node_t);
static void add_kw_sel_patches(BOOL, size_t, const char *, const char *);
static void add_lookup_imp_patches(size_t, size_t, size_t, size_t,
                                   const char *, objc_method_sig_t,
                                   objc_node_t);
static void add_direct_binding_imp_patches(size_t, size_t,
                                           size_t, size_t,
                                           objc_method_t);
static void validate_class_type(struct class_validation_result *,
                                BOOL, objc_class_t, objc_node_t, objc_node_t);
static void add_rest_patches();
static void write_init_class(objc_class_t, FILE *);
static void write_intern_sel(unsigned long, const char *, FILE *);
static void write_add_methods(FILE *, objc_class_t, BOOL);

extern char yytext[];
extern FILE *yyin;

static FILE *parsing_file;
static const char *parsing_path;
static objc_node_t root_node;

static BOOL has_error            = NO;
static int found_typedef         = 0;
static int nimpl_classes         = 0;
static int nimpl_ivars           = 0;
static int nimpl_inst_meths      = 0;
static int nimpl_class_meths     = 0;
static objc_class_t cur_cls      = NULL;
static BOOL cur_cls_declared     = NO;
static BOOL cur_cls_abstract     = NO;
static BOOL cur_cls_sealed       = NO;
static char *method_name         = NULL;
static BOOL is_inst_meth         = NO;
static BOOL is_meth_def_scope    = NO;
static int storage_cls_spcr_flag = 0;
static int type_spcr_flag        = 0;
static BOOL gen_sel_table        = NO;
static size_t cls_name_lc        = 0;
static size_t sel_table_lc       = 0;
static size_t ic_table_lc        = 0;
static size_t kw_dclr_colon_lc   = 0;
static BOOL kw_meth_first_brace  = NO;
static objc_class_t recv_cls     = NULL;
static BOOL recv_is_inst         = NO;
static objc_node_t ret_type      = NULL;
static objc_node_t func_args_node = NULL;

objc_scope_t objc_root_scope         = NULL;
objc_scope_t objc_current_scope      = NULL;
int objc_added_nlines                = 0;
st_table_t objc_objc_str_table       = NULL;
st_table_t objc_protocol_table       = NULL;
int objc_nobjc_strs                  = 0;
st_table_t objc_declared_class_table = NULL;
int objc_in_decl_spcr_list           = 0;
int objc_successes_decl_spcr         = 0;
unsigned long objc_nsend_sites       = 0;

%}

%union {
  objc_node_t node;
  const char *csval;
  char *sval;
  int ival;
  objc_str_list_t strs;
  size_t lc; /* location */
  struct {
    size_t lc; /* location */
    union {
      char *s;
      size_t len;
    } v;
  } range;
};

%token <range> IDENTIFIER TYPE_NAME CLASS_NAME 
%token <range> STR_LIT INT_CONST FLOAT_CONST CHAR_CONST ENUM_CONST
%token <lc> SIZEOF PTR_OP INC_OP DEC_OP LEFT_OP RIGHT_OP
%token <lc> PLUS_OP MINUS_OP TIMES_OP DIV_OP MOD_OP
%token <lc> LT_OP GT_OP LE_OP GE_OP EQ_OP NE_OP
%token <lc> AND_OP OR_OP NOT_OP LAND_OP LOR_OP LNOT_OP XOR_OP
%token <lc> ASSIGN_OP MUL_ASSIGN_OP DIV_ASSIGN_OP MOD_ASSIGN_OP ADD_ASSIGN_OP
%token <lc> SUB_ASSIGN_OP LEFT_ASSIGN_OP RIGHT_ASSIGN_OP AND_ASSIGN_OP
%token <lc> XOR_ASSIGN_OP OR_ASSIGN_OP
%token <lc> LPAREN RPAREN LBRACE RBRACE LBRACK RBRACK
%token <lc> COMMA PERIOD SEMI COLON COLON2 ELLIPSIS

%token <lc> TYPEDEF EXTERN STATIC AUTO REGISTER
%token <lc> CHAR SHORT INT LONG UNSIGNED FLOAT DOUBLE VOID
%token <lc> STRUCT UNION ENUM ELIPSIS RANGE
%token <lc> SIGNED SIGNED2 SIGNED3
%token <lc> CONST CONST2 CONST3
%token <lc> VOLATILE VOLATILE2 VOLATILE3
%token <lc> INLINE INLINE2 INLINE3
%token <lc> EXTENSION FUNCTION PRETTY_FUNCTION

%token <lc> CASE DEFAULT IF ELSE SWITCH WHILE DO FOR GOTO CONTINUE BREAK RETURN

%token <range> CATEGORY CLASS ENCODE END EXPORT INTERFACE IMPL IMPORT
%token <range> PRIVATE PROTECTED PROTOCOL PUBLIC SEL SUPER
%token <range> PUBLIC_QUAL PROTECTED_QUAL PRIVATE_QUAL SEALED ABSTRACT
%token <range> XSTR_LIT 

%token GCC_ATTR GCC_ASM GCC_ASM2 GCC_ASM3

%type <node> abstract_declarator
%type <node> additive_expression
%type <node> and_expression
%type <node> argument_expression_list
%type <node> assignment_expression
%type <csval> assignment_operator
%type <node> base_declarator
%type <node> cast_expression
%type <node> category_interface
%type <node> category_interface_declaration_list
%type <range> class_interface_specifier
%type <node> category_implementation
%type <range> category_name
%type <node> category_specifier
%type <node> class_declaration
%type <node> class_declarator_list
%type <node> class_interface
%type <node> class_interface_declaration
%type <node> class_implementation
%type <node> class_implementation_specifier
%type <range> class_name
%type <node> class_specifier
%type <node> compound_statement
%type <node> conditional_expression
%type <node> constant
%type <node> constant_expression
%type <node> declaration
%type <node> declaration_specifier
%type <node> declaration_specifier_list
%type <node> declarator
%type <node> declaration_list
%type <node> direct_abstract_declarator
%type <node> direct_declarator
%type <node> enumerator
%type <node> enumerator_list
%type <node> enum_specifier
%type <node> equality_expression
%type <node> exclusive_or_expression
%type <node> expression
%type <node> external_declaration
%type <node> expression_statement
%type <node> function_definition
%type <csval> gcc_asm
%type <node> gcc_asm_clobber_list
%type <node> gcc_asm_operand
%type <node> gcc_asm_operand_list
%type <node> gcc_asm_option_list
%type <node> gcc_asm_statement
%type <csval> gcc_asm_volatile
%type <node> gcc_assembly
%type <sval> gcc_attribute_name
%type <node> gcc_attribute_specifier
%type <node> gcc_direct_attribute_specifier
%type <node> gcc_direct_attribute_specifier_list
%type <node> identifier_list
%type <node> implementation_definition
%type <node> implementation_definition_list
%type <node> inclusive_or_expression
%type <node> init_declarator
%type <node> init_declarator_list
%type <node> initializer
%type <node> initializer_list
%type <node> instance_variable_declaration
%type <node> instance_variable_declaration_list
%type <node> instance_variable_list
%type <node> interface_declaration
%type <node> interface_declaration_list
%type <node> iteration_statement
%type <node> jump_statement
%type <node> keyword_argument_expression
%type <node> keyword_argument_expression_list
%type <node> keyword_declarator
%type <node> keyword_selector
%type <node> labeled_statement
%type <node> logical_or_expression
%type <node> logical_and_expression
%type <node> method_declaration
%type <node> method_declarator
%type <node> method_definition
%type <node> method_qualifier
%type <node> method_qualifier_list
%type <node> method_selector
%type <node> method_specifier
%type <node> method_type
%type <node> message_expression
%type <node> multiplicative_expression
%type <node> parameter_declaration
%type <node> parameter_list
%type <node> parameter_type_list
%type <node> pointer
%type <node> postfix_expression
%type <node> primary_expression
%type <node> protocol_declaration
%type <node> protocol_list
%type <range> protocol_name
%type <node> protocol_reference_list
%type <node> receiver
%type <ival> receiver_type
%type <node> relational_expression
%type <node> selection_statement
%type <sval> selector_name
%type <node> shift_expression
%type <node> specifier_qualifier_list
%type <node> statement
%type <node> statement_list
%type <node> storage_class_specifier
%type <node> string
%type <node> struct_declarator
%type <node> struct_declarator_list
%type <node> struct_declaration
%type <node> struct_declaration_list
%type <range> struct_name
%type <ival> struct_or_union
%type <node> struct_or_union_specifier
%type <node> transaction_unit
%type <node> type_name
%type <node> type_specifier
%type <node> type_qualifier
%type <node> type_qualifier_list
%type <node> unary_expression
%type <csval> unary_operator
%type <node> visibility_specification

%start transaction_unit

%%

transaction_unit
    : external_declaration
      {
        if ($1 != NULL) {
          $$ = objc_make_tunit_node($1);
          root_node = $$;
          root_node->scope = objc_root_scope;
        }
      }
    | transaction_unit external_declaration
      {
        if ($1 != NULL && $2 != NULL) {
          objc_node_add($1, $2);
          $$ = $1;
        } else if ($1 != NULL)
          $$ = $1;
        else if ($2 != NULL)
          $$ = objc_make_tunit_node($2);
        else
          $$ = NULL;

        if ($$ != NULL) {
          root_node = $$;
          root_node->scope = objc_root_scope;
        }
      }
    ;

external_declaration
    : function_definition       { $$ = NULL; }
    | declaration               { $$ = NULL; }
    | class_declaration         { $$ = $1; }
    | class_interface           { $$ = $1; cur_cls = NULL; }
    | class_implementation      { $$ = $1; cur_cls = NULL; }
    | category_interface        { $$ = $1; cur_cls = NULL; }
    | category_implementation   { $$ = $1; cur_cls = NULL; }
    | protocol_declaration      { $$ = $1; }
    ;

function_definition
    : declarator
      {
        set_func(NULL, $1, NULL);
      }
      compound_statement
    | declarator declaration_list
      {
        set_func(NULL, $1, $2);
      }
      compound_statement
    | declaration_specifier_list declarator
      {
        set_func($1, $2, NULL);
      }
      compound_statement
    | declaration_specifier_list declarator declaration_list
      {
        set_func($1, $2, $3);
      }
      compound_statement
    ;

declaration
    : declaration_specifier_list SEMI
      {
        $$ = objc_make_decl_node($1, NULL);
      }
    | declaration_specifier_list init_declarator_list SEMI
      {
        int i;
        objc_node_t init_dclr;

        /* patch for selector and inline cache tables */
        if (found_typedef && gen_sel_table && !sel_table_lc)
          sel_table_lc = $3;

        $$ = objc_make_decl_node($1, $2);
        for (i = 0; i < $2->a.l.nsubs; i++) {
          init_dclr = $2->a.l.subs[i];
          if (found_typedef)
            objc_scope_set_type(objc_current_scope, $1, init_dclr,
                                objc_node_dclr_name(init_dclr));
          else
            objc_scope_set_var(objc_current_scope, $1, init_dclr,
                               objc_node_dclr_name(init_dclr));
        }
        found_typedef = 0;
      }
    ;

declaration_specifier_list
    : declaration_specifier
      {
        $$ = objc_make_decl_spcr_list_node($1);
      }
    | declaration_specifier declaration_specifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
      }
    ;

declaration_specifier
    : storage_class_specifier   { $$ = $1; }
    | type_specifier            { $$ = $1; }
    | type_qualifier            { $$ = $1; }
    ;

storage_class_specifier
    : TYPEDEF
      {
        validate_storage_cls_spcr(TYPEDEF);
        found_typedef = 1;
        $$ = objc_make_storage_cls_spcr_node(TYPEDEF);
      }
    | EXTERN
      {
        validate_storage_cls_spcr(EXTERN);
        $$ = objc_make_storage_cls_spcr_node(EXTERN);
      }
    | STATIC
      {
        validate_storage_cls_spcr(STATIC);
        $$ = objc_make_storage_cls_spcr_node(STATIC);
      }
    | AUTO
      {
        validate_storage_cls_spcr(AUTO);
        $$ = objc_make_storage_cls_spcr_node(AUTO);
      }
    | REGISTER
      {
        validate_storage_cls_spcr(REGISTER);
        $$ = objc_make_storage_cls_spcr_node(REGISTER);
      }
    ;

type_specifier
    : CHAR          { $$ = validate_type_spcr_and_make_node(CHAR); }
    | SHORT         { $$ = validate_type_spcr_and_make_node(SHORT); }
    | INT           { $$ = validate_type_spcr_and_make_node(INT); }
    | LONG          { $$ = validate_type_spcr_and_make_node(LONG); }
    | SIGNED        { $$ = validate_type_spcr_and_make_node(SIGNED); }
    | SIGNED2       { $$ = validate_type_spcr_and_make_node(SIGNED2); }
    | SIGNED3       { $$ = validate_type_spcr_and_make_node(SIGNED3); }
    | UNSIGNED      { $$ = validate_type_spcr_and_make_node(UNSIGNED); }
    | FLOAT         { $$ = validate_type_spcr_and_make_node(FLOAT); }
    | DOUBLE        { $$ = validate_type_spcr_and_make_node(DOUBLE); }
    | VOID          { $$ = validate_type_spcr_and_make_node(VOID); }
    | struct_or_union_specifier { $$ = $1; }
    | enum_specifier            { $$ = $1; }
    | TYPE_NAME     { $$ = objc_make_typedef_name_node($1.v.s); }
    | CLASS_NAME    { $$ = objc_make_cls_name_node($1.v.s); }
    ;

type_qualifier
    : CONST         { $$ = objc_make_type_qual_node(CONST); }
    | CONST2        { $$ = objc_make_type_qual_node(CONST2); }
    | CONST3        { $$ = objc_make_type_qual_node(CONST3); }
    | VOLATILE      { $$ = objc_make_type_qual_node(VOLATILE); }
    | VOLATILE2     { $$ = objc_make_type_qual_node(VOLATILE2); }
    | VOLATILE3     { $$ = objc_make_type_qual_node(VOLATILE3); }
    | INLINE        { $$ = objc_make_type_qual_node(INLINE); }
    | INLINE2       { $$ = objc_make_type_qual_node(INLINE2); }
    | INLINE3       { $$ = objc_make_type_qual_node(INLINE3); }
    | EXTENSION     { $$ = objc_make_type_qual_node(EXTENSION); }
    ;

struct_or_union_specifier
    : struct_or_union struct_name LBRACE struct_declaration_list RBRACE
      {
        $$ = objc_make_su_spcr_node($1, $2.v.s, $4);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
        
        if (!ic_table_lc && STREQ($2.v.s, "objc_inline_cache"))
          ic_table_lc = $5;
      }
    | struct_or_union LBRACE struct_declaration_list RBRACE
      {
        $$ = objc_make_su_spcr_node($1, NULL, $3);
      }
    | struct_or_union struct_name
      {
        $$ = objc_make_su_spcr_node($1, $2.v.s, NULL);
      }
    ;

struct_or_union
    : STRUCT        { $$ = 1; }
    | UNION         { $$ = 0; }
    ;

struct_name
    : IDENTIFIER    { $$ = $1; }
    | TYPE_NAME     { $$ = $1; }
    ;

struct_declaration_list
    : struct_declaration
      {
        $$ = objc_make_struct_decl_list_node($1);
      }
    | struct_declaration_list struct_declaration
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

init_declarator_list
    : init_declarator
      {
        $$ = objc_make_init_dclr_list_node($1);
      }
    | init_declarator_list COMMA init_declarator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

init_declarator
    : declarator
      {
        $$ = objc_make_init_dclr_node($1, NULL);
      }
    | declarator ASSIGN_OP initializer
      {
        $$ = objc_make_init_dclr_node($1, $3);
      }
    ;

struct_declaration
    : specifier_qualifier_list struct_declarator_list SEMI
      {
        $$ = objc_make_struct_decl_node($1, $2);
      }
    ;

specifier_qualifier_list
    : type_specifier
      {
        $$ = objc_make_spcr_qual_list_node($1);
      }
    | type_specifier specifier_qualifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
      }
    | type_qualifier
      {
        $$ = objc_make_spcr_qual_list_node($1);
      }
    | type_qualifier specifier_qualifier_list
      {
        objc_node_insert($2, $1);
        $$ = $2;
      }
    ;

struct_declarator_list
    : struct_declarator
      {
        $$ = objc_make_struct_dclr_list_node($1);
      }
    | struct_declarator_list COMMA struct_declarator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

struct_declarator
    : declarator
      {
        $$ = objc_make_struct_dclr_node($1, NULL);
      }
    | COLON constant_expression
      {
        $$ = objc_make_struct_dclr_node(NULL, $2);
      }
    | declarator COLON constant_expression
      {
        $$ = objc_make_struct_dclr_node($1, $3);
      }
    ;

enum_specifier
    : ENUM LBRACE enumerator_list RBRACE
      {
        $$ = objc_make_enum_spcr_node(NULL, $3);
      }
    | ENUM IDENTIFIER LBRACE enumerator_list RBRACE
      {
        $$ = objc_make_enum_spcr_node($2.v.s, $4);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
      }
    | ENUM IDENTIFIER
      {
        $$ = objc_make_enum_spcr_node($2.v.s, NULL);
        objc_scope_set_struct(objc_current_scope, $$, $2.v.s);
      }
    ;

enumerator_list
    : enumerator
      {
        $$ = objc_make_enum_list_node($1);
      }
    | enumerator_list COMMA enumerator
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

enumerator
    : IDENTIFIER
      {
        $$ = objc_make_enum_node($1.v.s, NULL);
      }
    | IDENTIFIER ASSIGN_OP constant_expression
      {
        $$ = objc_make_enum_node($1.v.s, $3);
      }
    ;

declarator
    : base_declarator
      {
        $$ = objc_make_dclr_node($1, NULL, NULL);
      }
    | base_declarator gcc_assembly
      {
        $$ = objc_make_dclr_node($1, NULL, $2);
      }
    | base_declarator gcc_attribute_specifier 
      {
        $$ = objc_make_dclr_node($1, $2, NULL);
      }
    | base_declarator gcc_assembly gcc_attribute_specifier 
      {
        $$ = objc_make_dclr_node($1, $3, $2);
      }
    | base_declarator gcc_attribute_specifier gcc_assembly 
      {
        $$ = objc_make_dclr_node($1, $2, $3);
      }
    | gcc_attribute_specifier base_declarator
      {
        $$ = objc_make_dclr_node($2, $1, NULL);
      }
    | gcc_attribute_specifier base_declarator gcc_assembly
      {
        $$ = objc_make_dclr_node($2, $1, $3);
      }
    ;

base_declarator
    : direct_declarator
      {
        $$ = objc_make_base_dclr_node(NULL, $1);
      }
    | pointer direct_declarator
      {
        $$ = objc_make_base_dclr_node($1, $2);
      }
    ;

direct_declarator
    : IDENTIFIER
      {
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
        else if (found_typedef && !gen_sel_table && STREQ($1.v.s, "SEL"))
          gen_sel_table = YES;
        $$ = objc_make_ident_node($1.v.s);
      }
    | LPAREN declarator RPAREN
      {
        $$ = $2;
      }
    | direct_declarator LBRACK RBRACK
      {
        $$ = objc_make_array_dir_dclr_node($1, NULL);
      }
    | direct_declarator LBRACK constant_expression RBRACK
      {
        $$ = objc_make_array_dir_dclr_node($1, $3);
      }
    | direct_declarator LPAREN RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, NULL);
      }
    | direct_declarator LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, $3);
      }
    | direct_declarator LPAREN identifier_list RPAREN
      {
        $$ = objc_make_func_dir_dclr_node($1, $3);
      }
    ;

pointer
    : TIMES_OP
      {
        $$ = objc_make_ptr_node(NULL, NULL, NULL);
      }
    | TIMES_OP type_qualifier_list
      {
        $$ = objc_make_ptr_node(NULL, $2, NULL);
      }
    | TIMES_OP pointer
      {
        $$ = objc_make_ptr_node(NULL, NULL, $2);
      }
    | TIMES_OP type_qualifier_list pointer
      {
        $$ = objc_make_ptr_node(NULL, $2, $3);
      }
    | TIMES_OP gcc_attribute_specifier
      {
        $$ = objc_make_ptr_node($2, NULL, NULL);
      }
    | TIMES_OP gcc_attribute_specifier pointer
      {
        $$ = objc_make_ptr_node($2, $3, NULL);
      }
    | TIMES_OP gcc_attribute_specifier type_qualifier_list pointer
      {
        $$ = objc_make_ptr_node($2, $3, $4);
      }
    ;

type_qualifier_list
    : type_qualifier
      {
        $$ = objc_make_type_qual_list_node($1);
      }
    | type_qualifier_list type_qualifier
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

parameter_type_list
    : parameter_list
      {
        $$ = objc_make_param_type_list_node($1, 0);
      }
    | parameter_list COMMA ELLIPSIS
      {
        $$ = objc_make_param_type_list_node($1, 1);
      }
    ;

parameter_list
    : parameter_declaration
      {
        $$ = objc_make_param_list_node($1);
      }
    | parameter_list COMMA parameter_declaration
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

parameter_declaration
    : declaration_specifier_list
      {
        $$ = objc_make_param_decl_node($1, NULL);
      }
    | declaration_specifier_list declarator
      {
        $$ = objc_make_param_decl_node($1, $2);
      }
    | declaration_specifier_list abstract_declarator
      {
        $$ = objc_make_param_decl_node($1, $2);
      }
    ;

identifier_list
    : IDENTIFIER
      {
        $$ = objc_make_ident_list_node($1.v.s);
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
      }
    | identifier_list COMMA IDENTIFIER
      {
        $1->a.strs = objc_str_list_add($1->a.strs, $3.v.s);
        $$ = $1;
        if (is_meth_def_scope)
          add_ivar_ref_patch($3.lc, $3.v.s);
      }
    ;

initializer
    : assignment_expression                 { $$ = $1; }
    | LBRACE initializer_list RBRACE        { $$ = objc_make_intr_node($2); }
    | LBRACE initializer_list COMMA RBRACE  { $$ = objc_make_intr_node($2); }
    ;

initializer_list
    : initializer
      {
        $$ = objc_make_intr_list_node($1);
      }
    | initializer_list COMMA initializer
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

type_name
    : specifier_qualifier_list
      {
        $$ = objc_make_type_name_node($1, NULL);
      }
    | specifier_qualifier_list abstract_declarator
      {
        $$ = objc_make_type_name_node($1, $2);
      }
    ;

abstract_declarator
    : pointer
      {
        $$ = objc_make_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator
      {
        $$ = objc_make_abs_dclr_node(NULL, $1);
      }
    | pointer direct_abstract_declarator
      {
        $$ = objc_make_abs_dclr_node($1, $2);
      }
    ;

direct_abstract_declarator
    : LPAREN abstract_declarator RPAREN
      {
        $$ = $2;
      }
    | LBRACK RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node(NULL, NULL);
      }
    | LBRACK constant_expression RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node(NULL, $2);
      }
    | direct_abstract_declarator LBRACK RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator LBRACK constant_expression RBRACK
      {
        $$ = objc_make_const_dir_abs_dclr_node($1, $3);
      }
    | LPAREN RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node(NULL, NULL);
      }
    | LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node(NULL, $2);
      }
    | direct_abstract_declarator LPAREN RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node($1, NULL);
      }
    | direct_abstract_declarator LPAREN parameter_type_list RPAREN
      {
        $$ = objc_make_param_dir_abs_dclr_node($1, $3);
      }
    ;

statement
    : labeled_statement     { $$ = $1; }
    | expression_statement  { $$ = $1; }
    | compound_statement    { $$ = $1; }
    | selection_statement   { $$ = $1; }
    | iteration_statement   { $$ = $1; }
    | jump_statement        { $$ = $1; }
    | gcc_asm_statement     { $$ = $1; }
    ;

labeled_statement
    : IDENTIFIER labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(0, NULL, $3);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | CASE constant_expression labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(CASE, $2, $4);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | DEFAULT labeled_statement_separator statement
      {
        $$ = objc_make_label_stmt_node(DEFAULT, NULL, $3);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

labeled_statement_separator
    : COLON { begin_scope(); }
    ;

expression_statement
    : SEMI               { $$ = objc_make_expr_stmt_node(NULL); }
    | expression SEMI    { $$ = objc_make_expr_stmt_node($1); }
    ;

compound_statement
    : LBRACE RBRACE
      {
        if (kw_meth_first_brace) {
          objc_add_patch($1, 1, ") {");
          kw_meth_first_brace = NO;
        }
        $$ = objc_make_compound_stmt_node(NULL, NULL);
      }
    | begin_compound_statement statement_list RBRACE
      {
        $$ = objc_make_compound_stmt_node(NULL, $2);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | begin_compound_statement declaration_list RBRACE
      {
        $$ = objc_make_compound_stmt_node($2, NULL);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | begin_compound_statement declaration_list statement_list RBRACE
      {
        $$ = objc_make_compound_stmt_node($2, $3);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

begin_compound_statement
    : LBRACE
      {
        begin_scope();
        if (kw_meth_first_brace) {
          objc_add_patch($1, 1, ") { void *" RECV_VAR
                         " __attribute__((unused));");
          kw_meth_first_brace = NO;
        } else
          objc_add_patch($1, 1, " { void *" RECV_VAR
                         " __attribute__((unused));");
      }
    ;

statement_list
    : statement
      {
        $$ = objc_make_stmt_list_node($1);
      }
    | statement_list statement
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

selection_statement
    : IF begin_selection_statement expression RPAREN statement
      {
        $$ = objc_make_select_stmt_node(IF, $3, $5, NULL);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | IF begin_selection_statement expression RPAREN statement ELSE statement
      {
        $$ = objc_make_select_stmt_node(IF, $3, $5, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | SWITCH begin_selection_statement expression RPAREN statement
      {
        $$ = objc_make_select_stmt_node(SWITCH, $3, $5, NULL);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

begin_selection_statement
    : LPAREN { begin_scope(); }
    ;

iteration_statement
    : WHILE begin_iteration_statement expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(WHILE, NULL, $3, NULL, $5);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | DO statement WHILE begin_iteration_statement expression RPAREN SEMI
      {
        $$ = objc_make_iter_stmt_node(DO, NULL, $5, NULL, $2);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, NULL, NULL, $6);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, NULL, $5, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI expression SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, $4, NULL, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement SEMI expression SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, NULL, $4, $6, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, NULL, NULL, $7);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, NULL, $6, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI expression SEMI RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, $5, NULL, $8);
        $$->scope = objc_current_scope;
        end_scope();
      }
    | FOR begin_iteration_statement expression SEMI expression SEMI expression RPAREN statement
      {
        $$ = objc_make_iter_stmt_node(FOR, $3, $5, $7, $9);
        $$->scope = objc_current_scope;
        end_scope();
      }
    ;

begin_iteration_statement
    : LPAREN { begin_scope(); }
    ;

jump_statement
    : GOTO IDENTIFIER SEMI
      {
        $$ = objc_make_jump_stmt_node(GOTO, NULL, $2.v.s);
      }
    | CONTINUE SEMI
      {
        $$ = objc_make_jump_stmt_node(CONTINUE, NULL, NULL);
      }
    | BREAK SEMI
      {
        $$ = objc_make_jump_stmt_node(BREAK, NULL, NULL);
      }
    | RETURN SEMI
      {
        $$ = objc_make_jump_stmt_node(RETURN, NULL, NULL);
      }
    | RETURN expression SEMI
      {
        /*
        struct class_validation_result res;
        char *cast;
        */
        $$ = objc_make_jump_stmt_node(RETURN, $2, NULL);
        /*
        res.cls = NULL;
        validate_class_type(&res, is_meth_def_scope, cur_cls, ret_type, $2);
        switch (res.type) {
        case CLASS_OBJECT:
          objc_add_patch($1, 6, "return (Class)");
          break;

        case ROOT_CLASS:
          objc_add_patch($1, 6, "return (id)");
          break;

        case COMPATIBLE_CLASS:
          cast = SALLOC_N(char, strlen(res.cls->name)+16);
          if (res.cls == objc_root_class)
            sprintf(cast, "return (id)");
          else
            sprintf(cast, "return (%s*)", res.cls->name);
          objc_add_patch($1, 6, cast);
          objc_sfree(cast);
        break;

        case INCOMPATIBLE_TYPE:
          parse_error("incompatible type in return");
          break;

        case INCOMPATIBLE_CLASS:
          parse_error("incompatible class in return");
          break;
        }
        */
      }
    ;

expression
    : assignment_expression
      {
        $$ = objc_make_expr_node($1);
      }
    | expression COMMA assignment_expression
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

assignment_expression
    : conditional_expression { $$ = $1; }
    | unary_expression assignment_operator assignment_expression
      {
        $$ = objc_make_assign_expr_node($1, $2, $3);
      }
    ;

assignment_operator
    : ASSIGN_OP        { $$ = "="; }
    | MUL_ASSIGN_OP    { $$ = "*="; }
    | DIV_ASSIGN_OP    { $$ = "/="; }
    | MOD_ASSIGN_OP    { $$ = "%="; }
    | ADD_ASSIGN_OP    { $$ = "+="; }
    | SUB_ASSIGN_OP    { $$ = "-="; }
    | LEFT_ASSIGN_OP   { $$ = "<<="; }
    | RIGHT_ASSIGN_OP  { $$ = ">>="; }
    | AND_ASSIGN_OP    { $$ = "&="; }
    | XOR_ASSIGN_OP    { $$ = "^="; }
    | OR_ASSIGN_OP     { $$ = "|="; }
    ;

conditional_expression
    : logical_or_expression { $$ = $1; }
    | logical_or_expression '?' logical_or_expression COLON conditional_expression
      {
        $$ = objc_make_cond_expr_node($1, $3, $5);
      }
    ;

constant_expression
    : conditional_expression    { $$ = $1; }
    ;

logical_or_expression
    : logical_and_expression    { $$ = $1; }
    | logical_or_expression LOR_OP logical_and_expression
      {
        $$ = objc_make_log_or_expr_node($1, $3);
      }
    ;

logical_and_expression
    : inclusive_or_expression   { $$ = $1; }
    | logical_and_expression LAND_OP inclusive_or_expression
      {
        $$ = objc_make_log_and_expr_node($1, $3);
      }
    ;

inclusive_or_expression
    : exclusive_or_expression   { $$ = $1; }
    | inclusive_or_expression OR_OP exclusive_or_expression
      {
        $$ = objc_make_incl_or_expr_node($1, $3);
      }
    ;

exclusive_or_expression
    : and_expression    { $$ = $1; }
    | exclusive_or_expression XOR_OP and_expression
      {
        $$ = objc_make_exc_or_expr_node($1, $3);
      }
    ;

and_expression
    : equality_expression   { $$ = $1; }
    | and_expression AND_OP equality_expression
      {
        $$ = objc_make_and_expr_node($1, $3);
      }
    ;

equality_expression
    : relational_expression { $$ = $1; }
    | equality_expression EQ_OP relational_expression
      {
        $$ = objc_make_eq_expr_node($1, "==", $3);
      }
    | equality_expression NE_OP relational_expression
      {
        $$ = objc_make_eq_expr_node($1, "!=", $3);
      }
    ;

relational_expression
    : shift_expression  { $$ = $1; }
    | relational_expression LT_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, "<", $3);
      }
    | relational_expression GT_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, ">", $3);
      }
    | relational_expression LE_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, "<=", $3);
      }
    | relational_expression GE_OP shift_expression
      {
        $$ = objc_make_rel_expr_node($1, ">=", $3);
      }
    ;

shift_expression
    : additive_expression   { $$ = $1; }
    | shift_expression LEFT_OP additive_expression
      {
        $$ = objc_make_shift_expr_node($1, "<<", $3);
      }
    | shift_expression RIGHT_OP additive_expression
      {
        $$ = objc_make_shift_expr_node($1, ">>", $3);
      }
    ;

additive_expression
    : multiplicative_expression { $$ = $1; }
    | additive_expression PLUS_OP multiplicative_expression
      {
        $$ = objc_make_add_expr_node($1, "+", $3);
      }
    | additive_expression MINUS_OP multiplicative_expression
      {
        $$ = objc_make_add_expr_node($1, "-", $3);
      }
    ;

multiplicative_expression
    : cast_expression   { $$ = $1; }
    | multiplicative_expression TIMES_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "*", $3);
      }
    | multiplicative_expression DIV_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "/", $3);
      }
    | multiplicative_expression MOD_OP cast_expression
      {
        $$ = objc_make_multi_expr_node($1, "%", $3);
      }
    ;

cast_expression
    : unary_expression  { $$ = $1; }
    | LPAREN type_name RPAREN cast_expression
      {
        $$ = objc_make_cast_expr_node($2, $4);
      }
    ;

unary_expression
    : postfix_expression        { $$ = $1; }
    | INC_OP unary_expression
      {
        $$ = objc_make_inc_or_dec_expr_node(1, "++", $2);
      }
    | DEC_OP unary_expression   
      {
        $$ = objc_make_inc_or_dec_expr_node(1, "--", $2);
      }
    | unary_operator cast_expression
      {
        $$ = objc_make_inc_or_dec_expr_node(1, $1, $2);
      }
    | SIZEOF unary_expression
      {
        $$ = objc_make_size_expr_node($2);
      }
    | SIZEOF LPAREN type_name RPAREN
      {
        $$ = objc_make_size_type_expr_node($3);
      }
    | SEL LPAREN selector_name RPAREN
      {
        $$ = objc_make_sel_expr_node($3);
      }
    | PROTOCOL LPAREN CLASS_NAME RPAREN
      {
        $$ = objc_make_prtc_expr_node($3.v.s);
      }
    | ENCODE LPAREN type_name RPAREN
      {
        $$ = objc_make_enc_expr_node($3);
      }
    | message_expression        { $$ = $1; }
    ;

unary_operator
    : AND_OP        { $$ = "&"; }
    | TIMES_OP      { $$ = "*"; }
    | PLUS_OP       { $$ = "+"; }
    | MINUS_OP      { $$ = "-"; }
    | NOT_OP        { $$ = "~"; }
    | LNOT_OP       { $$ = "!"; }
    ;

selector_name
    : IDENTIFIER
      {
        $$ = objc_strdup($1.v.s);
      }
    | selector_name IDENTIFIER COLON
      {
        size_t len;
        char *sel;

        len = strlen($1) + strlen($2.v.s) + 2;
        sel = ALLOC_N(char, len);
        sprintf(sel, "%s%s:", $1, $2.v.s);
        $$ = sel;
      }
    ;

postfix_expression
    : primary_expression    { $$ = $1; }
    | postfix_expression LBRACK expression RBRACK
      {
        $$ = objc_make_array_ref_expr_node($1, $3);
      }
    | postfix_expression LPAREN RPAREN
      {
        $$ = objc_make_func_call_expr_node($1, NULL);
      }
    | postfix_expression LPAREN argument_expression_list RPAREN
      {
        $$ = objc_make_func_call_expr_node($1, $3);
      }
    | postfix_expression PERIOD IDENTIFIER
      {
        $$ = objc_make_su_ref_expr_node($1, 1, $3.v.s);
      }
    | postfix_expression PTR_OP IDENTIFIER
      {
        $$ = objc_make_su_ref_expr_node($1, 0, $3.v.s);
      }
    | postfix_expression INC_OP
      {
        $$ = objc_make_inc_or_dec_expr_node(0, "++", $1);
      }
    | postfix_expression DEC_OP
      {
        $$ = objc_make_inc_or_dec_expr_node(0, "--", $1);
      }
    ;

primary_expression
    : IDENTIFIER
      {
        $$ = objc_make_ident_node($1.v.s);
        if (is_meth_def_scope)
          add_ivar_ref_patch($1.lc, $1.v.s);
      }
    | constant                  { $$ = $1; }
    | string                    { $$ = $1; }
    | LPAREN expression RPAREN  { $$ = $2; }
    ;

argument_expression_list
    : assignment_expression
      {
        $$ = objc_make_arg_expr_list_node($1);
      }
    | argument_expression_list COMMA assignment_expression
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

constant
    : INT_CONST     { $$ = objc_make_const_node(INT_CONST, $1.v.s); }
    | FLOAT_CONST   { $$ = objc_make_const_node(FLOAT_CONST, $1.v.s); }
    | CHAR_CONST    { $$ = objc_make_const_node(CHAR_CONST, $1.v.s); }
    | ENUM_CONST    { $$ = objc_make_const_node(ENUM_CONST, $1.v.s); }
    ;

string
    : STR_LIT           { $$ = objc_make_str_node($1.v.s); }
    | FUNCTION          { /* FIXME */ $$ = objc_make_str_node("__FUNCTION__"); }
    | PRETTY_FUNCTION   { /* FIXME */ $$ = objc_make_str_node("__PRETTY_FUNCTION__"); }
    | XSTR_LIT
      {
      /*
        objc_node_t node = NULL;

        st_lookup(objc_objc_str_table, $1.range.s, (st_data_t *)&node);
        if (node == NULL) {
          node = objc_make_objc_str_node($1.range.lc, $1.s, objc_nobjc_strs);
          st_insert(objc_objc_str_table, $1.s, node);
          objc_nobjc_strs++;
        }
        $$ = node;
        */
      }
    ;

declaration_list
    : declaration
      {
        $$ = objc_make_decl_list_node($1);
      }
    | declaration_list declaration
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

message_expression
    : LBRACK receiver IDENTIFIER RBRACK
      {
        objc_method_sig_t sig;

        sig = objc_get_method_sig($3.v.s);
        if (sig != NULL) {
          $$ = objc_make_unary_msg_expr_node($2, $3.v.s);
          $$->a.unary_msg_expr.num = objc_nsend_sites;
          objc_assign_sel_num($3.v.s);
        } else
          parse_error("'%s' can not respond to '%c%s'",
                      recv_cls->name, recv_is_inst ? '-' : '+', $3.v.s);

        /* patch */
        add_lookup_imp_patches($1, $4, $3.lc, strlen($3.v.s),
                               $3.v.s, sig, $2);
      }
    | LBRACK receiver keyword_argument_expression_list RBRACK
      {
        objc_str_stream_t s;
        int i;
        char *sel;
        objc_method_sig_t sig;
        objc_node_t arg;
        size_t kw_arg_lc = 0, kw_arg_len = 0;

        s = objc_make_str_stream();
        for (i = 0; i < $3->a.l.nsubs; i++)
          objc_str_stream_write(s, $3->a.l.subs[i]->a.kw_arg_expr.name);
        sel = objc_str_stream_copy_value(s);
        objc_free_str_stream(s);
        objc_assign_sel_num(sel);

        sig = objc_get_method_sig(sel);
        if (sig != NULL) {
          $$ = objc_make_kw_msg_expr_node($2, sel, $3);
          $$->a.kw_msg_expr.num = objc_nsend_sites;
        } else
          parse_error("'%s' can not respond to '%c%s'",
                      recv_cls->name, recv_is_inst ? '-' : '+', sel);

        /* patch */
        for (i = 0; i < $3->a.l.nsubs; i++) {
          arg = $3->a.l.subs[i];
          if (i == 0) {
            kw_arg_lc = arg->a.kw_arg_expr.name_lc;
            kw_arg_len = strlen(arg->a.kw_arg_expr.name)-1;
            objc_add_patch(arg->a.kw_arg_expr.colon_lc, 1, ", ");
          } else {
            objc_add_patch(arg->a.kw_arg_expr.name_lc,
                           strlen(arg->a.kw_arg_expr.name)-1, ", ");
            objc_add_patch(arg->a.kw_arg_expr.colon_lc, 1, NULL);
          }
        }
        add_lookup_imp_patches($1, $4, kw_arg_lc, kw_arg_len,
                               sel, sig, $2);
      }
    ;

receiver
    : expression
      {
        $$ = $1;
        recv_is_inst = is_inst_meth; /* for 'self' */
        recv_cls = objc_class_type_from_node($$, &recv_is_inst, cur_cls);
        if (recv_cls == NULL)
          parse_error("receiver is not object");
      }
    | CLASS_NAME
      {
        char *repl;

        $$ = objc_make_cls_obj_recv_node($1.v.s);
        repl = SALLOC_N(char, strlen($1.v.s) + 24);
        sprintf(repl, "objc_lookup_class(\"%s\")", $1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), repl);
        objc_sfree(repl);

        recv_cls = objc_get_class($1.v.s);
        recv_is_inst = NO;
      }
    | SUPER
      {
        $$ = objc_make_super_node();
        objc_add_patch($1.lc, $1.v.len, "(id)self");
        recv_cls = cur_cls->superclass;
        recv_is_inst = is_inst_meth;
      }
    ;

keyword_argument_expression_list
    : keyword_argument_expression
      {
        $$ = objc_make_kw_arg_expr_list_node($1);
      }
    | keyword_argument_expression_list keyword_argument_expression
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

keyword_argument_expression
    : IDENTIFIER COLON argument_expression_list
      {
        size_t len;

        len = strlen($1.v.s);
        $1.v.s[len] = ':';
        $1.v.s[len+1] = 0;
        $$ = objc_make_kw_arg_expr_node($1.lc, $2, $1.v.s, $3);
      }
    ;

class_declaration
    : CLASS class_declarator_list SEMI
      {
        objc_str_list_t strs;
        char *repl;

        strs = $2->a.strs;
        while (strs) {
          st_insert(objc_declared_class_table, strs->car, strs->car);
          strs = strs->cdr;
        }
        $$ = objc_make_cls_decl_node($2);

        /* patch */
        repl = SALLOC_N(char, strlen($2->a.strs->car)+20);
        sprintf(repl, "typedef struct %s ", $2->a.strs->car);
        objc_add_patch($1.lc, $1.v.len, repl);
        objc_sfree(repl);
      }
    ;

class_declarator_list
    : IDENTIFIER
      {
        $$ = objc_make_cls_dclr_list_node($1.v.s);
      }
    | class_declarator_list COMMA IDENTIFIER
      {
        char *repl;

        objc_str_list_add_set(&$1->a.strs, $3.v.s);
        $$ = $1;

        repl = SALLOC_N(char, strlen($3.v.s)+24);
        sprintf(repl, "; typedef struct %s ", $3.v.s);
        objc_add_patch($2, 1, repl);
        objc_sfree(repl);
      }
    ;

class_interface
    : class_interface_specifier class_specifier END
      {
        char *defs;

        $$ = objc_make_cls_intf_node($2, NULL);
        cur_cls->is_abstract = cur_cls_abstract;
        cur_cls->is_sealed = cur_cls_sealed;

        if (cur_cls_declared)
          objc_add_patch($1.lc, $1.v.len, "struct ");
        else
          objc_add_patch($1.lc, $1.v.len, "typedef struct ");

        defs = get_all_ivar_defs(cur_cls, cur_cls_declared);
        objc_add_patch($3.lc, $3.v.len, defs);
        objc_free(defs);
      }
    | class_interface_specifier class_specifier class_interface_declaration END
      {
        char *repl, *defs;

        $$ = objc_make_cls_intf_node($2, $3);
        cur_cls->is_abstract = cur_cls_abstract;
        cur_cls->is_sealed = cur_cls_sealed;

        if ($3->a.cls_intf_decl.ivar_list != NULL)
          if (cur_cls_declared)
            objc_add_patch($1.lc, $1.v.len, "struct ");
          else
            objc_add_patch($1.lc, $1.v.len, "typedef struct ");
        else {
          defs = get_all_ivar_defs(cur_cls, cur_cls_declared);
          repl = SALLOC_N(char, strlen(defs) + 20);
          sprintf(repl, "%sstruct %s %s", 
                  cur_cls_declared ? "" : "typedef ", cur_cls->name, defs);
          objc_add_patch($1.lc, $1.v.len, repl);
          objc_add_patch(cls_name_lc, strlen(cur_cls->name), NULL);
          objc_free(defs);
          objc_sfree(repl);
        }

        objc_add_patch($4.lc, $4.v.len, NULL);
      }
    ;

class_interface_specifier
    : INTERFACE
      {
        $$ = $1;
      }
    | ABSTRACT INTERFACE
      {
        $$ = $2;
        cur_cls_abstract = YES;
        cur_cls_sealed = NO;
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | SEALED INTERFACE
      {
        $$ = $2;
        cur_cls_abstract = NO;
        cur_cls_sealed = YES;
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

class_specifier
    : class_name
      {
        cls_name_lc = $1.lc;
        $$ = objc_make_cls_spcr_node($1.v.s, NULL);
        cur_cls = objc_scope_set_class(objc_current_scope, $$);
        cur_cls_declared = st_lookup(objc_declared_class_table, $1.v.s, NULL);
      }
    | class_name COLON CLASS_NAME
      {
        /* validate sealed */
        if (objc_get_class($3.v.s)->is_sealed)
          parse_error("'%s' is sealed", $3.v.s);

        cls_name_lc = $1.lc;
        $$ = objc_make_cls_spcr_node($1.v.s, $3.v.s);
        cur_cls = objc_scope_set_class(objc_current_scope, $$);
        cur_cls_declared = st_lookup(objc_declared_class_table, $1.v.s, NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    ;

class_name
    : IDENTIFIER { $$ = $1; }
    | CLASS_NAME { $$ = $1; }
    ;

class_interface_declaration
    : protocol_reference_list
      {
        $$ = objc_make_cls_intf_decl_node($1, NULL, NULL);
      }
    | protocol_reference_list instance_variable_list
      {
        $$ = objc_make_cls_intf_decl_node($1, $2, NULL);
      }
    | protocol_reference_list instance_variable_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node($1, $2, $3);
      }
    | protocol_reference_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node($1, NULL, $2);
      }
    | instance_variable_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, $1, NULL);
      }
    | instance_variable_list interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, $1, $2);
      }
    | interface_declaration_list
      {
        $$ = objc_make_cls_intf_decl_node(NULL, NULL, $1);
      }
    ;

protocol_reference_list
    : LT_OP protocol_list GT_OP
      { 
        $$ = $2;
      }
    ;

protocol_list
    : CLASS_NAME
      { 
        $$ = objc_make_prtc_list_node($1.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | protocol_list COMMA CLASS_NAME
      { 
        objc_node_add_str($1, $3.v.s);
        $$ = $1;
        objc_add_patch($2, 1, NULL);
      }
    ;

instance_variable_list
    : LBRACE RBRACE
      {
        if (cur_cls->superclass)
          add_superclass_ivar_patch($1, cur_cls->superclass);
        add_class_struct_patch($2);
        $$ = objc_make_ivar_decl_list_node(NULL);
      }
    | LBRACE instance_variable_declaration_list RBRACE
      {
        int i, j, vsbl = OBJC_PUBLIC;
        objc_node_t ivar_decl, dclr_list, spcr_qual_list, dclr;
        objc_ivar_t ivar;
        size_t len;
        char *buf;

        /* generate instance variable representation for subclasses */
        len = $3 - $1;
        if (cur_cls->superclass) {
          add_superclass_ivar_patch($1, cur_cls->superclass);
          buf = cur_cls->ivars_repr = ALLOC_N(char,
            len + strlen(cur_cls->superclass->ivars_repr) + 1);
          buf += sprintf(buf, "%s", cur_cls->superclass->ivars_repr);
        } else
          buf = cur_cls->ivars_repr = ALLOC_N(char, len + 1);
        fseek(parsing_file, $1+1, SEEK_SET);
        fread(buf, len-1, 1, parsing_file);
        buf[len] = 0;
        buf = cur_cls->ivars_repr;
        while (*buf) {
          switch (*buf) {
          case '\r':
          case '\n':
            *buf = ' ';
          }
          buf++;
        }

        /* create instance variable objects */
        for (i = 0; i < $2->a.l.nsubs; i++) {
          ivar_decl = $2->a.l.subs[i];
          if (ivar_decl->type == OBJC_VSBL_SPEC_NODE)
            vsbl = ivar_decl->a.ival;
          if (ivar_decl->type == OBJC_STRUCT_DECL_NODE) {
            dclr_list = ivar_decl->a.struct_decl.dclr_list;
            for (j = 0; j < dclr_list->a.l.nsubs; j++) {
              spcr_qual_list = ivar_decl->a.struct_decl.spcr_qual_list;
              dclr = dclr_list->a.l.subs[j];
              ivar = ALLOC(struct objc_ivar);
              ivar->node = objc_make_ivar_decl_node(spcr_qual_list, dclr);
              ivar->vsbl = vsbl;
              ivar->name = objc_node_dclr_name(dclr);
              ivar->type_name = objc_node_type_name(ivar->node);
              ivar->ctype = NULL;
              ivar->enc = objc_make_type_encoding_from_node(ivar->node);
              objc_ivar_list_add_set(&cur_cls->ivars, ivar);
            }
          }
        }

        add_class_struct_patch($3);
        $$ = $2;
      }
    ;

instance_variable_declaration_list
    : instance_variable_declaration
      { 
        $$ = objc_make_ivar_decl_list_node($1);
        nimpl_ivars++;
      }
    | instance_variable_declaration_list instance_variable_declaration
      { 
        objc_node_add($1, $2);
        $$ = $1;
        nimpl_ivars++;
      }
    ;

instance_variable_declaration
    : struct_declaration        { $$ = $1; }
    | visibility_specification  { $$ = $1; }
    ;

visibility_specification
    : PRIVATE
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PRIVATE);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PROTECTED
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PROTECTED);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PUBLIC
      {
        $$ = objc_make_vsbl_spec_node(OBJC_PUBLIC);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

interface_declaration_list
    : interface_declaration
      {
        $$ = objc_make_intf_decl_list_node($1);
      }
    | interface_declaration_list interface_declaration
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

interface_declaration
    : declaration           { $$ = $1; }
    | method_declaration    { $$ = $1; }
    ;

method_declaration
    : method_declarator SEMI
      {
        objc_node_t spcr, quals, qual;
        objc_method_t method, supermeth;
        objc_method_sig_t sig;
        int i;

        /* get or register method signature */
        sig = objc_get_method_sig(method_name);
        if (sig == NULL) {
          objc_register_sel_name(method_name);
          sig = objc_register_method_sig(method_name, $1);
        }

        spcr = $1->a.mth_dclr.mth_spcr;
        method = ALLOC(struct objc_method);
        method->owner = cur_cls;
        method->is_inst = spcr->a.mth_spcr.rtype;
        method->has_impl = NO;
        method->node = $1;
        method->sig = sig;
        method->vsbl = OBJC_PUBLIC;
        method->is_abstract = NO;
        method->is_sealed = NO;
        method->imp = objc_make_imp_name(spcr->a.mth_spcr.rtype,
                                         cur_cls->name,
                                         method_name);

        quals = $1->a.mth_dclr.mth_qual_list;
        if (quals) {
          for (i = 0; i < quals->a.l.nsubs; i++) {
            qual = quals->a.l.subs[i];
            if (qual->a.tag == PUBLIC_QUAL)
              method->vsbl = OBJC_PUBLIC;
            else if (qual->a.tag == PROTECTED_QUAL)
              method->vsbl = OBJC_PROTECTED;
            else if (qual->a.tag == PRIVATE_QUAL)
              method->vsbl = OBJC_PRIVATE;
            else if (qual->a.tag == ABSTRACT)
              method->is_abstract = YES;
            else if (qual->a.tag == SEALED)
              method->is_sealed = YES;
          }
        }

        /* validate private and sealed */
        if (cur_cls->superclass) {
          supermeth = objc_class_get_method(cur_cls->superclass,
                                            method_name,
                                            spcr->a.mth_spcr.rtype);
          if (supermeth) {
            if (supermeth->vsbl == OBJC_PRIVATE)
              parse_error("cannot override private method '%s'", method_name);
            if (supermeth->is_sealed)
              parse_error("cannot override sealed method '%s'", method_name);
          }
        }

        if (spcr->a.mth_spcr.rtype)
          objc_method_list_add_set(&cur_cls->inst_meths, method);
        else
          objc_method_list_add_set(&cur_cls->class_meths, method);

        $$ = objc_make_mth_decl_node($1);

        objc_add_patch($2, 1, ");");
      }
    ;

method_declarator
    : method_qualifier_list method_specifier method_selector
      {
        $$ = objc_make_mth_dclr_node($1, $2, $3);
      }
    | method_specifier method_selector
      {
        $$ = objc_make_mth_dclr_node(NULL, $1, $2);
      }
    ;

method_qualifier_list 
    : method_qualifier
      {
        $$ = objc_make_mth_qual_list_node($1);
      }
    | method_qualifier_list method_qualifier
      {
        $$ = $1;
        objc_node_add($1, $2);
      }
    ;

method_qualifier
    : PUBLIC_QUAL
      {
        $$ = objc_make_mth_qual_node(PUBLIC_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PROTECTED_QUAL
      {
        $$ = objc_make_mth_qual_node(PROTECTED_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | PRIVATE_QUAL
      {
        $$ = objc_make_mth_qual_node(PRIVATE_QUAL);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | ABSTRACT
      {
        $$ = objc_make_mth_qual_node(ABSTRACT);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    | SEALED
      {
        $$ = objc_make_mth_qual_node(SEALED);
        objc_add_patch($1.lc, $1.v.len, NULL);
      }
    ;

method_specifier
    : receiver_type
      {
        $$ = objc_make_mth_spcr_node($1, NULL);
        ret_type = NULL;
      }
    | receiver_type method_type
      {
        $$ = objc_make_mth_spcr_node($1, $2);
        ret_type = $2;
      }
    ;

receiver_type
    : PLUS_OP
      {
        $$ = 0;
        is_inst_meth = NO;
        kw_dclr_colon_lc = 0;
        objc_add_patch($1, 1, NULL);
      }
    | MINUS_OP
      {
        $$ = 1;
        is_inst_meth = YES;
        kw_dclr_colon_lc = 0;
        objc_add_patch($1, 1, NULL);
      }
    ;

class_implementation
    : IMPL class_implementation_specifier END
      {
        $$ = objc_make_cls_impl_node($2, NULL);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($3.lc, $3.v.len, NULL);
        validate_all_meth_defs();
        nimpl_classes++;
      }
    | IMPL class_implementation_specifier implementation_definition_list END
      {
        $$ = objc_make_cls_impl_node($2, $3);
        objc_add_patch($1.lc, $1.v.len, NULL);
        objc_add_patch($4.lc, $4.v.len, NULL);
        validate_all_meth_defs();
        nimpl_classes++;
      }
    ;

class_implementation_specifier
    : CLASS_NAME
      {
        $$ = objc_make_cls_spcr_node($1.v.s, NULL);
        cur_cls = objc_get_class($1.v.s);
        if (cur_cls == NULL)
          parse_error("%s: undeclared", $1.v.s);

        cur_cls->has_impl = 1;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
      }
    | CLASS_NAME COLON CLASS_NAME
      {
        $$ = objc_make_cls_spcr_node($1.v.s, $3.v.s);
        cur_cls = objc_get_class($1.v.s);
        if (cur_cls == NULL)
          parse_error("%s: undeclared", $1.v.s);

        cur_cls->has_impl = 1;
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        objc_add_patch($2, 1, NULL);
        objc_add_patch($3.lc, strlen($3.v.s), NULL);
      }
    ;

implementation_definition_list
    : implementation_definition
      {
        if ($1 != NULL)
          $$ = objc_make_impl_def_list_node($1);
      }
    | implementation_definition_list implementation_definition
      {
        if ($1 != NULL && $2 != NULL) {
          objc_node_add($1, $2);
          $$ = $1;
        } else if ($1 != NULL)
          $$ = $1;
        else if ($2 != NULL)
          $$ = objc_make_impl_def_list_node($2);
        else
          $$ = NULL;
      }
    ;

implementation_definition
    : function_definition   { $$ = NULL; }
    | declaration           { $$ = NULL; }
    | method_definition
      {
        char *name;
        objc_method_t meth;
        BOOL is_inst;

        is_meth_def_scope = NO;
        $$ = $1;
        is_inst = $$->a.mth_def.mth_dclr->a.mth_dclr.mth_spcr->a.mth_spcr.rtype;
        name = objc_node_get_method_name($$);
        meth = objc_class_get_method(cur_cls, name, is_inst);
        meth->has_impl = YES;

        if (is_inst)
          nimpl_inst_meths++;
        else
          nimpl_class_meths++;
      }
    ;

method_definition
    : method_declarator
      {
        is_meth_def_scope = YES;
        kw_meth_first_brace = YES;
        func_args_node = $1->a.mth_dclr.mth_sel;
      }
      compound_statement
      {
        $$ = objc_make_mth_def_node($1, NULL, $3);
        is_meth_def_scope = NO;
      }
    | method_declarator
      {
        is_meth_def_scope = YES;
        kw_meth_first_brace = YES;
        func_args_node = $1->a.mth_dclr.mth_sel;
      }
      declaration_list compound_statement
      {
        $$ = objc_make_mth_def_node($1, $3, $4);
        is_meth_def_scope = NO;
      }
    ;

method_selector
    : IDENTIFIER
      {
        char *repl, *imp;

        method_name = $1.v.s;
        objc_register_sel_name(method_name);
        $$ = objc_make_mth_unary_sel_node($1.v.s);

        /* patch */
        imp = objc_make_imp_name(is_inst_meth, cur_cls->name, method_name);
        repl = SALLOC_N(char, strlen(imp) + strlen(cur_cls->name) + 24);
        sprintf(repl, "%s(%s *self " ATTR_UNUSED ", "
                      "SEL _cmd " ATTR_UNUSED, imp, cur_cls->name);
        objc_add_patch($1.lc, strlen($1.v.s), repl);
        objc_sfree(repl);
      }
    | keyword_selector
      {
        method_name = generate_kw_sel($1);
        objc_register_sel_name(method_name);
        $$ = objc_make_mth_kw_sel_node($1, method_name, 0);
        add_kw_sel_patches(is_inst_meth, kw_dclr_colon_lc,
                           cur_cls->name, method_name);
      }
    | keyword_selector COMMA ELLIPSIS
      {
        method_name = generate_kw_sel($1);
        objc_register_sel_name(method_name);
        $$ = objc_make_mth_kw_sel_node($1, method_name, 1);
        add_kw_sel_patches(is_inst_meth, kw_dclr_colon_lc,
                           cur_cls->name, method_name);
      }
    ;

keyword_selector
    : keyword_declarator
      {
        $$ = objc_make_kw_sel_node($1);
      }
    | keyword_selector keyword_declarator
      {
        objc_node_add($1, $2);
        $$ = $1;
      }
    ;

keyword_declarator
    : COLON IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node(NULL, NULL, $2.v.s);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $1;
        else
          objc_add_patch($1, 1, ", id ");
      }
    | COLON method_type IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node(NULL, $2, $3.v.s);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $1;
        else
          objc_add_patch($1, 1, ", ");
      }
    | IDENTIFIER COLON IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node($1.v.s, NULL, $3.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $2;
        else
          objc_add_patch($2, 1, ", id ");
      }
    | IDENTIFIER COLON method_type IDENTIFIER
      {
        $$ = objc_make_kw_dclr_node($1.v.s, $3, $4.v.s);
        objc_add_patch($1.lc, strlen($1.v.s), NULL);
        if (kw_dclr_colon_lc == 0)
          kw_dclr_colon_lc = $2;
        else
          objc_add_patch($2, 1, ", ");
      }
    ;

method_type
    : LPAREN type_name RPAREN
      {
        $$ = $2;
        objc_add_patch($1, 1, NULL);
        objc_add_patch($3, 1, " ");
      }
    ;

category_interface
    : INTERFACE category_specifier END
      {
        $$ = objc_make_cat_intf_node($2, NULL);
      }
    | INTERFACE category_specifier category_interface_declaration_list END
      {
        $$ = objc_make_cat_intf_node($2, $3);
      }
    ;

category_specifier
    : CLASS_NAME LPAREN category_name RPAREN
      {
        $$ = objc_make_cat_spcr_node($1.v.s, $3.v.s);
      }
    ;

category_interface_declaration_list
    : protocol_reference_list
      {
        $$ = objc_make_cat_intf_decl_list_node($1, NULL);
      }
    | protocol_reference_list interface_declaration_list
      {
        $$ = objc_make_cat_intf_decl_list_node($1, $2);
      }
    | interface_declaration_list
      {
        $$ = objc_make_cat_intf_decl_list_node(NULL, $1);
      }
    ;

category_name
    : IDENTIFIER    { $$ = $1; }
    | CLASS_NAME    { $$ = $1; }
    ;

category_implementation
    : IMPL category_specifier END
      {
        $$ = objc_make_cat_impl_node($2, NULL);
      }
    | IMPL category_specifier implementation_definition_list END
      {
        $$ = objc_make_cat_impl_node($2, $3);
      }
    ;

protocol_declaration
    : PROTOCOL protocol_name END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, NULL, NULL);
      }
    | PROTOCOL protocol_name protocol_reference_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, $3, NULL);
      }
    | PROTOCOL protocol_name protocol_reference_list interface_declaration_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, $3, $4);
      }
    | PROTOCOL protocol_name interface_declaration_list END
      {
        $$ = objc_make_prtc_decl_node($2.v.s, NULL, $3);
      }
    ;

protocol_name
    : IDENTIFIER    { validate_protocol($1.v.s); $$ = $1; }
    | CLASS_NAME    { validate_protocol($1.v.s); $$ = $1; }
    ;

gcc_attribute_specifier
    : GCC_ATTR LPAREN LPAREN RPAREN RPAREN
      {
        $$ = objc_make_gcc_attr_spcr_node(NULL);
      }
    | GCC_ATTR LPAREN LPAREN gcc_direct_attribute_specifier_list RPAREN RPAREN
      {
        $$ = objc_make_gcc_attr_spcr_node($4);
      }
    ;

gcc_direct_attribute_specifier_list
    : gcc_direct_attribute_specifier
      {
        $$ = objc_make_gcc_dir_attr_spcr_list_node($1);
      }
    | gcc_direct_attribute_specifier_list COMMA gcc_direct_attribute_specifier
      {
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

gcc_direct_attribute_specifier
    : gcc_attribute_name
      {
        $$ = objc_make_gcc_dir_attr_spcr_node($1, NULL);
      }
    | gcc_attribute_name constant_expression
      {
        $$ = objc_make_gcc_dir_attr_spcr_node($1, $2);
      }
    ;

gcc_attribute_name
    : IDENTIFIER    { $$ = $1.v.s; }
    | CONST         { $$ = objc_strdup("const"); }
    | CONST2        { $$ = objc_strdup("__const"); }
    | CONST3        { $$ = objc_strdup("__const__"); }
    ;

gcc_asm_statement
    : gcc_assembly SEMI
      {
        $$ = objc_make_gcc_asm_stmt_node($1);;
      }
    ;

gcc_assembly
    : gcc_asm LPAREN STR_LIT RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, NULL, $3.v.s, NULL);
      }
    | gcc_asm gcc_asm_volatile LPAREN STR_LIT RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, $2, $4.v.s, NULL);
      }
    | gcc_asm LPAREN STR_LIT COLON gcc_asm_option_list RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, NULL, $3.v.s, $5);
      }
    | gcc_asm gcc_asm_volatile LPAREN STR_LIT COLON gcc_asm_option_list RPAREN
      {
        $$ = objc_make_gcc_asm_node($1, $2, $4.v.s, $6);
      }
    ;

gcc_asm
    : GCC_ASM   { $$ = "asm"; }
    | GCC_ASM2  { $$ = "__asm"; }
    | GCC_ASM3  { $$ = "__asm__"; }
    ;

gcc_asm_volatile
    : VOLATILE          { $$ = "volatile"; }
    | VOLATILE2         { $$ = "__volatile"; }
    | VOLATILE3         { $$ = "__volatile__"; }
    ;

gcc_asm_option_list
    : gcc_asm_operand_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, NULL, NULL);
      }
    | gcc_asm_operand_list COLON gcc_asm_operand_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, $3, NULL);
      }
    | gcc_asm_operand_list COLON gcc_asm_operand_list COLON gcc_asm_clobber_list
      {
        $$ = objc_make_gcc_asm_opt_list_node($1, $3, $5);
      }
    ;

gcc_asm_operand_list
    : gcc_asm_operand
      {
        $$ = objc_make_gcc_asm_op_list_node($1);
      }
    | gcc_asm_operand_list COMMA gcc_asm_operand
      {    
        objc_node_add($1, $3);
        $$ = $1;
      }
    ;

gcc_asm_operand
    : STR_LIT LPAREN expression RPAREN
      {
        $$ = objc_make_gcc_asm_op_node($1.v.s, $3);
      }
    ;

gcc_asm_clobber_list
    : STR_LIT
      {
        $$ = objc_make_gcc_asm_clob_list_node($1.v.s);
      }
    | gcc_asm_clobber_list COMMA STR_LIT
      {
        $1->a.strs = objc_str_list_add($1->a.strs, $3.v.s);
        $$ = $1;
      }
    ;

%%

static void
begin_scope()
{
  if (is_meth_def_scope)
    objc_current_scope = objc_make_meth_def_scope(objc_current_scope);
  else
    objc_current_scope = objc_make_scope(objc_current_scope);

  if (func_args_node) {
    set_func_args(func_args_node);
    func_args_node = NULL;
  }
}

static void
end_scope()
{
  objc_current_scope = objc_current_scope->parent;
}

static void
set_func(objc_node_t decl_spcr_list,
         objc_node_t dclr,
         objc_node_t decl_list)
{
  if (dclr->a.dclr.base_dclr->a.base_dclr.dir_dclr->type !=
      OBJC_FUNC_DIR_DCLR_NODE)
    parse_error("syntax error");
  func_args_node = objc_make_func_def_node(decl_spcr_list,
                                           dclr, decl_list, NULL);
  objc_scope_set_var(objc_current_scope, func_args_node, dclr,
                     objc_node_get_ident_name(dclr));
}

static void
validate_storage_cls_spcr(int tag)
{
  if (storage_cls_spcr_flag == 0)
    storage_cls_spcr_flag |= tag;
  else if (tag == TYPEDEF &&
           storage_cls_spcr_flag == STORAGE_CLS_SPCR_TYPEDEF)
    parse_error("multiple storage classes in declaration specifiers");
}

static objc_node_t
validate_type_spcr_and_make_node(int tag)
{
  int flag, cnfl1, cnfl2, cnfl3;

  objc_begin_decl_spcr_list();
  flag = type_spcr_tag_to_flag(tag);
  if (type_spcr_flag == 0)
    type_spcr_flag |= flag;

  else if (tag == SHORT && (type_spcr_flag & TYPE_SPCR_SHORT))
    parse_error("duplicate 'short'");

  else if (tag == INT && (type_spcr_flag & TYPE_SPCR_INT))
    parse_error("duplicate 'int'");

  else if (tag == LONG && (type_spcr_flag & TYPE_SPCR_LONG))
    type_spcr_flag |= TYPE_SPCR_LONG_LONG;

  else if ((type_spcr_flag & flag) &&
           (tag == LONG && !(type_spcr_flag & TYPE_SPCR_LONG_LONG)))
    goto error;

  else
    type_spcr_flag |= flag;

  /* type specifier conflict */
  cnfl1 =  (type_spcr_flag & TYPE_SPCR_SIGNED) > 0;
  cnfl1 += (type_spcr_flag & TYPE_SPCR_UNSIGNED) > 0;
  if (cnfl1 > 1) goto error;

  cnfl2 =  (type_spcr_flag & TYPE_SPCR_SHORT) > 0;
  cnfl2 += (type_spcr_flag & TYPE_SPCR_LONG) > 0;
  if (cnfl2 > 1) goto error;
  
  cnfl3 =  (type_spcr_flag & TYPE_SPCR_FLOAT) > 0;
  cnfl3 += (type_spcr_flag & TYPE_SPCR_DOUBLE) > 0;
  if (cnfl1 > 1 || cnfl2 > 1 || cnfl3 > 1 ||
      (cnfl1+cnfl3) > 1 || (cnfl2+cnfl3) > 1)
    goto error;
  goto end;

error:
  parse_error("two or more data types in declaration specifiers");

end:
  return objc_make_type_spcr_node(tag);
}

static int
type_spcr_tag_to_flag(int tag)
{
  switch (tag) {
  case CHAR:        return TYPE_SPCR_CHAR;
  case SHORT:       return TYPE_SPCR_SHORT;
  case INT:         return TYPE_SPCR_INT;
  case LONG:        return TYPE_SPCR_LONG;
  case FLOAT:       return TYPE_SPCR_FLOAT;
  case DOUBLE:      return TYPE_SPCR_DOUBLE;
  case VOID:        return TYPE_SPCR_VOID;
  case UNSIGNED:    return TYPE_SPCR_UNSIGNED;
  case SIGNED: case SIGNED2: case SIGNED3:
    return TYPE_SPCR_SIGNED;
  default:
    return 0;
  }
}

void
objc_begin_decl_spcr_list()
{
  objc_in_decl_spcr_list = 1;
  storage_cls_spcr_flag = 0;
  type_spcr_flag = 0;
}

void
objc_end_decl_spcr_list()
{
  objc_in_decl_spcr_list = 0;
  storage_cls_spcr_flag = 0;
  type_spcr_flag = 0;
}

static char *
generate_kw_sel(objc_node_t node)
{
  objc_str_stream_t s;
  char *name;
  int i;

  s = objc_make_str_stream();
  for (i = 0; i < node->a.l.nsubs; i++) {
    objc_str_stream_write(s, node->a.l.subs[i]->a.kw_dclr.name);
    objc_str_stream_write(s, ":");
  }
  name = objc_str_stream_copy_value(s);
  objc_free(s);

  return name;
}

static void
validate_protocol(char *name)
{
  if (st_lookup(objc_protocol_table, (st_data_t)name, NULL))
    objc_print_error("Protocol `%s' is already defined", name);
}

void
yyerror(const char *s)
{
  fflush(stdout);
  printf("%s:%d:%d: %s\n",
         objc_current_line_mark->file,
         objc_current_line_mark->lineno,
         objc_parsing_column, s);
}

static void
parse_error(const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  parse_logv("error", s, ap);
  va_end(ap);
  has_error = YES;
}

static void
parse_warn(const char *s, ...)
{
  va_list ap;

  va_start(ap, s);
  parse_logv("warning", s, ap);
  va_end(ap);
}

static void
parse_logv(const char *tag, const char *s, va_list ap)
{
  fflush(stdout);
  printf("%s:%d:%d: %s: ",
         objc_current_line_mark->file,
         objc_current_line_mark->lineno,
         objc_parsing_column, tag);
  vprintf(s, ap);
  printf("\n");
}

objc_node_t
objc_parse_file(const char *path)
{
  int st;

#if YYDEBUG != 0
  yydebug = 1;
#endif

  parsing_path = path;
  root_node = NULL;
  is_meth_def_scope = 0;
  objc_added_nlines = 0;
  objc_root_scope = objc_current_scope = objc_make_root_scope();
  objc_objc_str_table = st_init_strtable();
  objc_protocol_table = st_init_strtable();
  objc_declared_class_table = st_init_strtable();
  objc_init_name();
  yyin = fopen(path, "r");
  if (yyin == NULL)
    objc_print_error("%s: No such file", path);
  parsing_file = fopen(path, "r");
  st = yyparse();
  fclose(yyin);
  fclose(parsing_file);
  add_rest_patches();
  if (st != 0 || has_error)
    exit(st);

  return root_node;
}

static char *
get_all_ivar_defs(objc_class_t cls, BOOL declared)
{
  objc_str_stream_t st;
  objc_class_t temp;

  st = objc_make_str_stream(NULL);
  objc_str_stream_write(st, "{");
  if (cls->superclass) {
    temp = cls->superclass;
    while (temp) {
      if (temp->ivars_repr)
        objc_str_stream_write(st, temp->ivars_repr);
        temp = temp->superclass;
      }
    }

  if (declared)
    objc_str_stream_write(st, "};");
  else {
    objc_str_stream_write(st, "}");
    objc_str_stream_write(st, cls->name);
    objc_str_stream_write(st, ";");
  }

  return objc_str_stream_copy_value_and_free(st);
}

static void
add_superclass_ivar_patch(size_t lbrace, objc_class_t scls)
{
  char *patch;

  patch = SALLOC_N(char, strlen(scls->ivars_repr) + 2);
  sprintf(patch, "{%s", scls->ivars_repr);
  objc_add_patch(lbrace, 1, patch);
  objc_sfree(patch);
}

static void
add_class_struct_patch(size_t lc)
{
  char *name;

  if (cur_cls_declared)
    objc_add_patch(lc, 1, "};");
  else {
    name = SALLOC_N(char, strlen(cur_cls->name)+3);
    sprintf(name, "}%s;", cur_cls->name);
    objc_add_patch(lc, 1, name);
    objc_sfree(name);
  }
}

static void
add_ivar_ref_patch(size_t lc, const char *var)
{
  char *repl;

  if (objc_class_get_ivar(cur_cls, var) != NULL) {
    repl = SALLOC_N(char, strlen(var)+7);
    sprintf(repl, "self->%s", var);
    objc_add_patch(lc, strlen(var), repl);
    objc_sfree(repl);
  }
}

static void
validate_all_meth_defs()
{
  validate_meth_defs(cur_cls, YES);
  validate_meth_defs(cur_cls, NO);
}

static void
validate_meth_defs(objc_class_t cls, BOOL is_inst)
{
  objc_method_list_t meths, ms, sms;
  objc_class_t scls;
  BOOL abs_def;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  /* validate implementation */
  ms = meths;
  while (ms) {
    if (!ms->car->has_impl)
      parse_error("'%c%s' is not implemented", is_inst ? '-' : '+',
                  ms->car->sig->name);
    ms = ms->cdr;
  }

  /* validate abstract */
  scls = cls->superclass;
  while (scls) {
    if (is_inst)
      sms = scls->inst_meths;
    else
      sms = scls->class_meths;
    while (sms) {
      if (sms->car->is_abstract) {
        abs_def = NO;
        ms = meths;
        while (ms) {
          if (STREQ(sms->car->sig->name, ms->car->sig->name))
            abs_def = YES;
          ms = ms->cdr;
        }
        if (!abs_def)
          parse_error("abstract '%c%s' is not implemented", is_inst ? '-' : '+',
                      sms->car->sig->name);
      }
      sms = sms->cdr;
    }
    scls = scls->superclass;
  }
}

static void
set_func_args(objc_node_t node)
{
  switch (node->type) {
  case OBJC_FUNC_DEF_NODE:
    {
      objc_node_t decl, dclr, params, param;
      int i, j;

      /* K&R */
      if (node->a.func_def.decl_list) {
        for (i = 0; i < node->a.func_def.decl_list->a.l.nsubs; i++) {
          decl = node->a.func_def.decl_list->a.l.subs[i];
          for (j = 0; j < decl->a.decl.init_dclr_list->a.l.nsubs; j++) {
            dclr = decl->a.decl.init_dclr_list->a.l.subs[j];
            objc_scope_set_var(objc_current_scope,
                               decl->a.decl.decl_spcr_list, dclr,
                               objc_node_get_ident_name(dclr));
          }
        }
      } else {
        params = node->a.func_def.dclr->a.dclr.base_dclr->
          a.base_dclr.dir_dclr->a.func_dir_dclr.param_list;
        if (params) {
          params = node->a.param_type_list.param_list;
          for (i = 0; i < params->a.l.nsubs; i++) {
            param = params->a.l.subs[i];
            dclr = param->a.param_decl.dclr;
            /* dclr == NULL when parameter list is "(void)" */
            if (dclr)
              objc_scope_set_var(objc_current_scope,
                                 param->a.param_decl.decl_spcr_list,
                                 dclr, objc_node_get_ident_name(dclr));
          }
        }
      }
    }
    break;

  case OBJC_MTH_UNARY_SEL_NODE:
    /* ignore */
    break;

  case OBJC_MTH_KW_SEL_NODE:
    {
      int i;
      objc_node_t kw_dclr;

      for (i = 0; i < node->a.mth_kw_sel.sel->a.l.nsubs; i++) {
        kw_dclr = node->a.mth_kw_sel.sel->a.l.subs[i];
        objc_scope_set_var(objc_current_scope,
                           kw_dclr->a.kw_dclr.mtype->a.type_name.spcr_qual_list,
                           kw_dclr->a.kw_dclr.mtype->a.type_name.abs_dclr,
                           kw_dclr->a.kw_dclr.arg);
      }
    }
    break;

  default:
    objc_print_error("set_func_args: not supported -- %s", objc_node_name(node));
  }
}

static void
add_kw_sel_patches(BOOL inst_meth, size_t colon_lc,
                   const char *cls, const char *meth)
{
  char *repl, *imp;
  size_t i;

  imp = objc_make_imp_name(inst_meth, cls, meth);
  repl = SALLOC_N(char, strlen(imp) + strlen(cls) + 32);
  sprintf(repl, "%s(%s *self " ATTR_UNUSED
                ", SEL _cmd " ATTR_UNUSED ",", imp, cls);
  for (i = 0; i < strlen(repl); i++) {
    if (repl[i] == ':')
      repl[i] = '_';
  }
  objc_add_patch(colon_lc, 1, repl);
  objc_sfree(repl);
}

static void
add_lookup_imp_patches(size_t lb_lc, size_t rb_lc,
                       size_t kw_lc, size_t kw_len,
                       const char *meth_name,
                       objc_method_sig_t sig,
                       objc_node_t recv)
{
  char *repl, *buf, *mtype_name = NULL;
  unsigned long sel_num;
  BOOL is_super, uses_dtable, sealed = NO;
  int dcode;
  const char *func;
  objc_method_t meth, m;
  objc_class_t cls;
  size_t bufsize;

  /* validate 'super' */
  is_super = recv->type == OBJC_SUPER_NODE;
  if (is_super && (!cur_cls || !is_meth_def_scope)) {
    parse_error("cannot use 'super' in this context");
    return;
  }

  /* validate method */
  meth = objc_class_get_method(recv_cls, meth_name, recv_is_inst);
  if (meth != NULL) {
    /* validate sending messages to public and private method */
    if (meth->vsbl == OBJC_PROTECTED &&
        !objc_class_is_kind_of(cur_cls, meth->owner)) {
      parse_error("cannot send message to protected method in this context");
      return;
    } else if (meth->vsbl == OBJC_PRIVATE &&
        !(meth->owner == recv_cls && meth->owner == cur_cls)) {
      parse_error("cannot send message to private method in this context");
      return;
    }

    /* direct binding (private or sealed) */
    sealed = meth->is_sealed || meth->owner->is_sealed;
    if (sealed && meth->owner->superclass) {
      cls = meth->owner->superclass;
      while (cls) {
        m = objc_class_get_method(cls, meth_name, recv_is_inst);
        if (m && !m->is_abstract) {
          sealed = NO;
          break;
        }
        cls = cls->superclass;
      }
    } 
    if (meth->vsbl == OBJC_PRIVATE || sealed) {
      add_direct_binding_imp_patches(lb_lc, rb_lc, kw_lc, kw_len, meth);
      return;
    }
  }

  objc_add_patch(lb_lc, 1, "(" RECV_VAR "=(void *)(");

  dcode = objc_get_dispatch_code(recv_cls, meth_name, recv_is_inst);
  uses_dtable = objc_main_opts.dtable && dcode >= 0 && !sealed;
  if (dcode < 0 && recv_cls != objc_root_class) {
    if (objc_main_opts.no_strict_msg)
      parse_warn("`%s' may not respond to `%c%s'",
                 recv_cls->name, recv_is_inst ? '-' : '+', meth_name);
    else {
      parse_error("`%s' does not assert response to `%c%s'",
                  recv_cls->name, recv_is_inst ? '-' : '+', meth_name);
      return;
    }
  }

  mtype_name = objc_node_type_name(sig->mtype);
  sel_num = objc_get_sel_num(meth_name);
  bufsize = strlen(mtype_name) * 2 + 200;
  if (cur_cls && cur_cls->superclass)
    bufsize += strlen(cur_cls->superclass->name);
  repl = buf = SALLOC_N(char, bufsize); 
  buf += sprintf(buf, "), ((%s)(*(%s (*)(id, SEL, ...))",
                 mtype_name, mtype_name);

  if (is_super) {
    if (uses_dtable)
      func = OBJC_DISPATCH_IMP_SUPER_FUNC;
    else if (objc_main_opts.inline_cache)
      func = OBJC_LOOKUP_IMP_SUPER_INLINE_FUNC;
    else
      func = OBJC_LOOKUP_IMP_SUPER_FUNC;
    buf += sprintf(buf, "%s(objc_lookup_class(\"%s\"), (id)self",
                   func, cur_cls->superclass->name);
  } else {
    if (uses_dtable)
      func = OBJC_DISPATCH_IMP_FUNC;
    else if (objc_main_opts.inline_cache)
      func = OBJC_LOOKUP_IMP_INLINE_FUNC;
    else
      func = OBJC_LOOKUP_IMP_FUNC;
    buf += sprintf(buf, "%s((id)" RECV_VAR, func);
  }

  if (uses_dtable)
    buf += sprintf(buf, ", %d", dcode);
  else {
    buf += sprintf(buf, ", (SEL)" SEL_TABLE_VAR "[%lu]", sel_num);
    if (objc_main_opts.inline_cache)
      buf += sprintf(buf, ", (InlineCache)&" IC_TABLE_VAR "[%lu]",
                     objc_nsend_sites++);
  }
  buf += sprintf(buf, "))((id)%s, (SEL)" SEL_TABLE_VAR "[%lu]",
                 is_super ? "self" : RECV_VAR, sel_num);
  objc_add_patch(kw_lc, kw_len, repl);
  objc_add_patch(rb_lc, 1, ")))");
  objc_sfree(repl);
}

static void
add_direct_binding_imp_patches(size_t lb_lc, size_t rb_lc,
                               size_t kw_lc, size_t kw_len,
                               objc_method_t meth)
{
  char *repl;

  repl = SALLOC_N(char, strlen(meth->imp) + 50);
  sprintf(repl, "%s(", meth->imp);
  objc_add_patch(lb_lc, 1, repl);

  sprintf(repl, ", (SEL)" SEL_TABLE_VAR "[%lu]",
          objc_get_sel_num(meth->sig->name));
  objc_add_patch(kw_lc, kw_len, repl);
  objc_sfree(repl);

  objc_add_patch(rb_lc, 1, ")");
}

static void
validate_class_type(struct class_validation_result *res,
                    BOOL is_meth,
                    objc_class_t context,
                    objc_node_t target,
                    objc_node_t expr)
{
  BOOL target_inst, expr_inst;
  objc_class_t target_cls = NULL, expr_cls;

  if (target)
    target_cls = objc_class_type_from_node(target, &target_inst, context);
  else if (is_meth) {
    res->type = ROOT_CLASS;
    return;
  }
  expr_cls = objc_class_type_from_node(expr, &expr_inst, context);

  if (target_cls && expr_cls) {
    if (!target_inst) {
      if (!expr_inst || expr_cls == objc_root_class)
        res->type = CLASS_OBJECT;
      else
        res->type = INCOMPATIBLE_CLASS;

    } else if (target_cls == objc_root_class)
      res->type = ROOT_CLASS;

    else if (expr_cls == objc_root_class) {
      res->type = COMPATIBLE_CLASS;
      res->cls = target_cls;

    } else if (target_cls == expr_cls)
      res->type = SAME_CLASS;

    else if (objc_class_is_kind_of(expr_cls, target_cls)) {
      res->type = COMPATIBLE_CLASS;
      res->cls = target_cls;

    } else
      res->type = INCOMPATIBLE_CLASS;
  } else if ((!target_cls && expr_cls) || (target_cls && !expr_cls))
    res->type = INCOMPATIBLE_TYPE;
  else
    res->type = BOTH_NOT_OBJECT;
}

static void
add_rest_patches()
{
  char *repl;

  repl = SALLOC_N(char, 200);

  /* selector table */
  sprintf(repl, "; static SEL " SEL_TABLE_VAR "[%lu];", objc_used_sel_num);
  objc_add_patch(sel_table_lc, 1, repl);

  /* inline cache table */
  if (objc_main_opts.inline_cache) {
    sprintf(repl, "}; static struct objc_inline_cache "
            IC_TABLE_VAR "[%lu]", objc_nsend_sites);
    objc_add_patch(ic_table_lc, 1, repl);
  }
  objc_sfree(repl);
}

void
objc_write_init_code(const char *path)
{
  FILE *f;

  f = fopen(path, "a");
  fprintf(f, "static void __attribute__((constructor))\n"
          "__objc_init_module()\n{\n");
  if (nimpl_classes > 0) {
    fprintf(f, "  static struct objc_proto_class "
               TEMP_CLASS "[%d];\n", nimpl_classes);
    if (nimpl_ivars > 0)
      fprintf(f, "  static struct objc_proto_ivar "
                 TEMP_IVAR "[%d];\n", nimpl_ivars);
    if (nimpl_inst_meths > 0)
      fprintf(f, "  static struct objc_proto_method "
                 TEMP_INST_METH "[%d];\n", nimpl_inst_meths);
    if (nimpl_class_meths > 0)
      fprintf(f, "  static struct objc_proto_method "
                 TEMP_CLASS_METH "[%d];\n", nimpl_class_meths);
  }

  /* initialize runtime */
  fprintf(f, "\n  objc_init_runtime();\n\n");

  /* intern selectors */
  objc_iter_sels((objc_sel_iter_f)write_intern_sel, (void *)f);
  fprintf(f, "\n");

  /* register classes */
  nimpl_classes = 0;
  nimpl_ivars = 0;
  nimpl_inst_meths = 0;
  nimpl_class_meths = 0;
  objc_iter_classes((objc_class_iter_f)write_init_class, (void *)f);

  fprintf(f, "}\n");
  fclose(f);
}

static void
write_intern_sel(unsigned long n, const char *name, FILE *f)
{
  if (n != OBJC_NOT_USED_SEL) {
    fprintf(f, "  " SEL_TABLE_VAR "[%lu] = ", n);
    if (objc_get_builtin_sel(name) != NULL)
      fprintf(f, "(SEL)objc_get_builtin_sel(\"%s\");\n", name);
    else
      fprintf(f, "objc_register_sel(\"%s\");\n", name);
  }
}

static void
write_init_class(objc_class_t cls, FILE *f)
{
  objc_ivar_list_t ivars;
  objc_ivar_t ivar;
  int nivars, ninst_meths, nclass_meths;

  /* interface only */
  if (!cls->has_impl)
    return;

  /* initialize prototype class */
  fprintf(f, "  " TEMP_CLASS "[%d].name = \"%s\";\n", nimpl_classes, cls->name);

  fprintf(f, "  " TEMP_CLASS "[%d].super_name = ", nimpl_classes);
  if (cls->superclass == NULL)
    fprintf(f, NULL_S ";\n");
  else 
    fprintf(f, "\"%s\";\n", cls->superclass->name);

  nivars = objc_ivar_list_get_size(cls->ivars);
  fprintf(f, "  " TEMP_CLASS "[%d].nivars = %d;\n",
          nimpl_classes, nivars);
  fprintf(f, "  " TEMP_CLASS "[%d].ivars = ", nimpl_classes);
  if (nivars)
    fprintf(f, "&" TEMP_IVAR "[%d];\n", nimpl_ivars);
  else
    fprintf(f, NULL_S ";\n");

  ninst_meths = objc_method_list_get_size(cls->inst_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].ninst_meths = %d;\n",
          nimpl_classes, ninst_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].inst_meths = ", nimpl_classes);
  if (ninst_meths)
    fprintf(f, "&" TEMP_INST_METH "[%d];\n", nimpl_inst_meths);
  else
    fprintf(f, NULL_S ";\n");

  nclass_meths = objc_method_list_get_size(cls->class_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].nclass_meths = %d;\n",
          nimpl_classes, nclass_meths);
  fprintf(f, "  " TEMP_CLASS "[%d].class_meths = ", nimpl_classes);
  if (nclass_meths)
    fprintf(f, "&" TEMP_CLASS_METH "[%d];\n", nimpl_class_meths);
  else
    fprintf(f, NULL_S ";\n");

  fprintf(f, "  " TEMP_CLASS "[%d].ninst_dcodes = %d;\n",
          nimpl_classes,
          cls->inst_dcodes ? cls->inst_dcodes->num_entries : 0);
  fprintf(f, "  " TEMP_CLASS "[%d].nclass_dcodes = %d;\n",
          nimpl_classes,
          cls->class_dcodes ? cls->class_dcodes->num_entries : 0);
  fprintf(f, "\n");

  /* add instance variables */
  ivars = cls->ivars;
  while (ivars) {
    ivar = ivars->car;
    fprintf(f, "  " TEMP_IVAR "[%d].name = \"%s\";\n",
            nimpl_ivars, ivar->name);
    fprintf(f, "  " TEMP_IVAR "[%d].size = sizeof(%s);\n",
            nimpl_ivars, ivar->type_name);
    fprintf(f, "  " TEMP_IVAR "[%d].type = \"%s\";\n",
            nimpl_ivars,  ivar->enc->full_code);
    nimpl_ivars++;
    ivars = ivars->cdr;
  }
  fprintf(f, "\n");

  /* add instance and class methods */
  write_add_methods(f, cls, YES);
  write_add_methods(f, cls, NO);

  /* registser prototype class */
  fprintf(f, "  objc_register_proto_class(&" TEMP_CLASS "[%d]);\n",
          nimpl_classes);
  nimpl_classes++;
}

static void
write_add_methods(FILE *f, objc_class_t cls, BOOL is_inst)
{
  objc_method_list_t meths;
  objc_method_t meth;
  st_table_t dcodes;
  int *nmeths;
  const char *var;

  if (is_inst) {
    meths = cls->inst_meths;
    dcodes = cls->inst_dcodes;
    nmeths = &nimpl_inst_meths;
    var = TEMP_INST_METH;
  } else {
    meths = cls->class_meths;
    dcodes = cls->class_dcodes;
    nmeths = &nimpl_class_meths;
    var = TEMP_CLASS_METH;
  }

  while (meths) {
    meth = meths->car;
    if (meth->has_impl) {
      fprintf(f, "  %s[%d].name = objc_get_sel(\"%s\");\n", var, *nmeths,
              meth->sig->name);
      fprintf(f, "  %s[%d].imp = (IMP)%s;\n", var, *nmeths, meth->imp);
      fprintf(f, "  %s[%d].types = \"%s\";\n", var, *nmeths,
              meth->sig->enc->full_code);
      fprintf(f, "  %s[%d].dcode = %d;\n", var, *nmeths,
              objc_get_dispatch_code(cls, meth->sig->name, is_inst));
      fprintf(f, "  %s[%d].is_private = %d;\n", var, *nmeths,
              meth->vsbl == OBJC_PRIVATE);
      fprintf(f, "  %s[%d].is_sealed = %d;\n", var, *nmeths,
              meth->is_sealed);
    }
    (*nmeths)++;
    meths = meths->cdr;
  }
  fprintf(f, "\n");
}

